package robocup.component.actionfactories;

import robocup.component.ControllerNumbers;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.STAMINA_LEVEL;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.KickAction;
import robocup.component.actions.MoveAction;
import robocup.component.actions.TackleAction;
import robocup.component.actions.TurnAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.StaticObject;
import robocup.component.worldobjects.Player.RELATIVE;

/**
 * The <code>BasicActionFactory</code> provides general methods for movement
 * (move/dash/turn) and kicking. It can be used by the states for simple and not
 * well planned activity.
 */
public class BasicActionFactory {

  //dummy for less Vektor creation
  private static Vektor dummyVektor = Vektor.getDummyInstance();
  private static boolean spelling_enable = false;
  private static Vektor[] spellings1 = new Vektor[128];
  private static Vektor[] spellings2 = new Vektor[128];
  private static Vektor[] spellings3 = new Vektor[128];
  private static Vektor[] spellings4 = new Vektor[128];
  private static Vektor[] spellings5 = new Vektor[128];
  private static Vektor[] spellings6 = new Vektor[128];
  private static Vektor[] spellings7 = new Vektor[128];
  private static Vektor[] spellings8 = new Vektor[128];
  private static Vektor[] spellings9 = new Vektor[128];
  private static Vektor[] spellings10 = new Vektor[128];
  private static Vektor[] spellings11 = new Vektor[128];
  
  /**
   * This method moves the player to a certain position (at the beginning of a
   * half or after a goal).
   * 
   * @param pos
   *          The Vektor representing the position.
   * @return the move action
   */
  public static MoveAction move(final Vektor pos) {

    final Vektor tmp = pos.cloned();
    tmp.rotate(90);

    return new MoveAction((int) Math.rint(tmp.x), (int) Math.rint(-tmp.y));
  }

  /* ********************** turn neck actions ********************** */

  /**
   * Turn neck to object.
   * 
   * @param world
   *          the worldmodel
   * @param firstAction
   *          the mainaction for this turn-neck
   * @param viewWidth
   *          the viewWidth
   * @param object
   *          the object that you want to see
   * 
   * @return turn neck action needed for seeing object null if object can't be
   *         seen. <code>null</code> if thr object cannot be seen.
   */
  public static TurnNeckAction turnNeckToObject(final WorldModel world,
      final Action firstAction,
      double viewWidth,
      final StaticObject object) {

    final Vektor nextObjectPos = world.predictNext(object, firstAction).getPosition();

    // me after action in next cycle
    final Player nextMe = world.getSelfRef().predictPlayerAfterAction(firstAction);

    // rel. angle player to Object
    final double angleToHead = Vektor.normalize(nextObjectPos.sub(
        nextMe.getPosition()).getAngle() - nextMe.getHeadDir());

    // rel. angle to body
    final double angleToBody = Vektor.normalize(nextObjectPos.sub(
        nextMe.getPosition()).getAngle() - nextMe.getBodyDir());

    // Object can't be seen in next cycle
    if ((angleToBody > viewWidth / 2 + SConf.getInstance().MAXNECKANG) || (angleToBody < -viewWidth / 2 + SConf
        .getInstance().MINNECKANG)) {
      return null;
    }

    final double oldNeckAngle = nextMe.getNeckAngle();
    // the angle that we would like to turn the neck to
    double resNeckAngle = Vektor.normalize(angleToHead + oldNeckAngle);

    // normalize |neckAngle| <= 90�
    if (resNeckAngle > SConf.getInstance().MAXNECKANG) {
      resNeckAngle = SConf.getInstance().MAXNECKANG;
    }
    else if (resNeckAngle < SConf.getInstance().MINNECKANG) {
      resNeckAngle = SConf.getInstance().MINNECKANG;
    }

    // the rel. turnNeckAngle
    double bestAngle = resNeckAngle - oldNeckAngle;

    // with the normal ServerConfig this won't be necessary
    // (bestAngle is always between -180� and + 180�)
    // but who knows.....
    if (bestAngle > SConf.getInstance().MAXNECKMOMENT) {
      bestAngle = SConf.getInstance().MAXNECKMOMENT;
    }
    else if (bestAngle < SConf.getInstance().MINNECKMOMENT) {
      bestAngle = SConf.getInstance().MINNECKMOMENT;
    }
    final TurnNeckAction turnNeckAction = new TurnNeckAction(bestAngle);
    return turnNeckAction;
  }

  /* ************************ turn actions ************************* */
  /**
   * This method produces an <code>Action</code> that makes the player turn
   * his body towards a given point. Note that the <code>Action</code> is not
   * sent.
   * 
   * @param world
   *          the worldmodel
   * @param point
   *          point to turn to
   * @return The <code>Action</code>.
   * 
   * THE TURN IS CALCULATED FOR THE CURRENT POS AND NOT FOR THE NEXT!!!!
   */
  public static TurnAction turnBodyToPoint(final WorldModel world,
      final Vektor point) {

    return BasicActionFactory.turnBodyToPoint(world.getSelfRef(), point);
  }

  /**
   * This method produces an <code>Action</code> that makes the player turn
   * his body towards a given point. Note that the <code>Action</code> is not
   * sent.
   * 
   * @param player
   *          the player
   * @param point -
   *          point to turn to
   * 
   * @return The <code>Action</code>.
   * 
   * THE TURN IS CALCULATED FOR THE CURRENT POS AND NOT FOR THE NEXT!!!!
   */
  public static TurnAction turnBodyToPoint(final Player player,
      final Vektor point) {

    final Vektor pToPoint = point.cloned();
    pToPoint.subFromThis(player.getPosition());
    final double turnAngle = player.getAngleForTurn(Vektor.normalize(pToPoint
        .getAngle() - player.getBodyDir()));
        
//    double turnAngle = player.getAngleForTurn(
//        player.getAngleRel(point, RELATIVE.TO_BODY));

    final TurnAction action = new TurnAction(turnAngle);
    return action;
  }

  /**
   * truns to a point so that the point is in front of the player in a given
   * number of cycles (consideres players moment)
   * 
   * @param player -
   *          the player
   * @param point -
   *          the point to turn to
   * @param delay -
   *          the delay
   * @return the turn action
   */
  public static TurnAction turnBodyToPoint(final Player player,
      final Vektor point,
      final int delay) {

    final Vektor resPoint = player.getSpeed().cloned();
    resPoint.multThis(player.relativePlayerMovementAfterNCycles(delay));

    resPoint.multThis(-1);
    resPoint.addToThis(point);
    return BasicActionFactory.turnBodyToPoint(player, resPoint);
  }

  /**
   * This method produces an <code>Action</code> that makes the player turn
   * his back towards a given point. Note that the <code>Action</code> is not
   * sent.
   * 
   * @param world -
   *          world model
   * @param point
   *          point to turn back to
   * @return The <code>Action</code>.
   * 
   * THE TURN IS CALCULATED FOR THE CURRENT POS AND NOT FOR THE NEXT!!!!
   */
  public static Action turnBackToPoint(final WorldModel world,
      final Vektor point) {

    return BasicActionFactory.turnBackToPoint(world.getSelfRef(), point);
  }

  /**
   * This method produces an <code>Action</code> that makes the player turn
   * his back towards a given point. Note that the <code>Action</code> is not
   * sent.
   * 
   * @param player -
   *          the player
   * @param point
   *          point to turn back to
   * @return The <code>Action</code>.
   * 
   * THE TURN IS CALCULATED FOR THE CURRENT POS AND NOT FOR THE NEXT!!!!
   */
  public static TurnAction turnBackToPoint(final Player player,
      final Vektor point) {

    final Vektor pToPoint = point.cloned();
    pToPoint.subFromThis(player.getPosition());
    final double turnAngle = player.getAngleForTurn(Vektor.normalize(pToPoint
        .getAngle() - player.getBodyDir() + 180.0));

    final TurnAction action = new TurnAction(turnAngle);
    return action;
  }

  /* *********************** dash actions ************************** */

  /**
   * This method produces an <code>Action</code> that makes the player dash to
   * the given position. Note that the <code>Action</code> is not sent.
   * 
   * @param p
   *          the player
   * 
   * @param point
   *          point where to go
   * @return The <code>Action</code>.
   */
  public static DashAction dashToPoint(final Player p,
      final Vektor point) {

    final int power = p.getPowerForDash(point);
    final DashAction action = new DashAction(power);
    return action;
  }

  /**
   * This method calculates the DashAction that should bring us as close to a
   * position as possible. it differs from the simple version of this method in
   * that the drift is considered. (i.e.: if you wan't to be at a position in 2
   * cycles it doesn't make sense to dash full first and stop afterwards (looses
   * 1 action) instead the speed resulting from dashing + the drift in the next
   * cycle should bring you to the position)
   * 
   * @param p
   *          the player
   * @param point
   *          the point to reach
   * @param time -
   *          time when to arrive at position
   * @return a dash action. <code>null</code> if the time is 0.
   */
  public static DashAction dashToPoint(final Player p,
      final Vektor point,
      final int time) {

    if (time == 0) {
      return null;
    }

    final int power = p.getPowerForDash(point, time);
    final DashAction action = new DashAction(power);
    return action;
  }

  /**
   * This method produces an <code>Action</code> that makes the player dash to
   * the given position. Note that the <code>Action</code> is not sent.
   * 
   * @param world
   *          the world model
   * 
   * @param point
   *          point where to go
   * @return The <code>Action</code>.
   */
  public static DashAction dashToPoint(final WorldModel world,
      final Vektor point) {

    return BasicActionFactory.dashToPoint(world.getSelfRef(), point);
  }

  /**
   * This method makes the player move to a given position <code>pos</code>.
   * 
   * @param world
   *          the world model
   * 
   * @param pos
   *          The position to go to.
   * @param staminaLevel
   *          the stamina level
   * @return the best action to reach this position
   */
  public static Action moveToPos(final WorldModel world,
      final Vektor pos,
      final ControllerNumbers.STAMINA_LEVEL staminaLevel) {

    final Action mainAction = BasicActionFactory.moveToPos(world, pos, 10, false);
    if (mainAction instanceof DashAction) {
      world.getSelfRef().correctDashPower(mainAction, staminaLevel);
    }

    return mainAction;
  }

  /**
   * This method produces an <code>Action</code> that makes the player move to
   * a given position. Note that the <code>Action</code> is not sent.
   * 
   * @param world
   *          the world model
   * @param q
   *          The position to move to.
   * @param angle
   *          If the target point is in front of the agent, then a dash is
   *          performed when the relative angle to this point is smaller than
   *          the given <code>angle</code>.
   * @param b
   *          If this value is <b>true </b>, the player always moves backwards
   *          to the target point, useful e.g. for the goalie.
   * @return The <code>Action</code>.
   */
  public static Action moveToPos(final WorldModel world,
      final Vektor q,
      final double angle,
      final boolean b) {

    // rw used next position of Player //lw why?
    final Vektor myNextPos = world.getMyNextPosition();
    final double bodyAngle = world.getBodyAngle();
    double ang = Vektor.normalize(myNextPos.getAngleTo(q) - bodyAngle);

    if (b) {
      ang = Vektor.normalize(ang + 180);
      if (Math.abs(ang) < angle) {
        return BasicActionFactory.dashToPoint(world, q);
      }
      return BasicActionFactory.turnBackToPoint(world, q);
    }
    if ((Math.abs(ang) < angle)) {
      return BasicActionFactory.dashToPoint(world, q);
    }
    return BasicActionFactory.turnBodyToPoint(world, q);
  }
  
  /**
   * This method produces an <code>Action</code> that makes the player move to
   * a given position. Note that the <code>Action</code> is not sent.
   * 
   * @param player
   *          the player
   * @param point
   *          The position to move to.
   * @param angle
   *          If the target point is in front of the agent, then a dash is
   *          performed when the relative angle to this point is smaller than
   *          the given <code>angle</code>.
   * @param b
   *          If this value is <b>true </b>, the player always moves backwards
   *          to the target point, useful e.g. for the goalie.
   * @param staminalevel
   * 		  The stamina level.
   * @return The <code>Action</code>.
   */
  public static Action moveToPos(final Player player,
      final Vektor point,
      final double angle,
      final boolean b,
      final STAMINA_LEVEL staminalevel) {

    dummyVektor.copy(player.getPosition());
    dummyVektor.addToThis(player.getSpeed());
    
    final double bodyAngle = player.getBodyDir();
    double ang = Vektor.normalize(dummyVektor.getAngleTo(point) - bodyAngle);

    Action a = null;
    if (b) {
      ang = Vektor.normalize(ang + 180);
      if (Math.abs(ang) < angle) {
        a = BasicActionFactory.dashToPoint(player, point);
      }
      else{
    	  a = BasicActionFactory.turnBackToPoint(player, point);
      }
    }
    if ((Math.abs(ang) < angle)) {
      a =BasicActionFactory.dashToPoint(player, point);
    }
    else{
    	a = BasicActionFactory.turnBodyToPoint(player, point);
    }
    
    if(a.getType() == Action.TYPE.DASH){
    	player.correctDashPower(a, staminalevel);
    }
    
    return a;
  }
  
  /**
   * This method produces an <code>Action</code> that makes the player move to
   * a given position. the tolerance if given as the accepted deviation from the
   * dash line.
   * 
   * @param player -
   *          the player
   * @param point -
   *          the pos to dash to
   * @param maxDist -
   *          the max tolerated distance between the point and the dashline
   * @param staminalevel -
   *          the stamina level, null -> no stamina control
   * @return a dash or a turn action.
   */
  public static Action moveToPos(final Player player,
      final Vektor point,
      final double maxDist,
      final STAMINA_LEVEL staminalevel) {

	if( player.getDistance(point) < maxDist +1.0){
		return BasicActionFactory.moveToPos(player, point, 10, false, staminalevel);
	}
    final double dirToPoint = player.getAngleRel(point, RELATIVE.TO_BODY);

    if (Math.abs(dirToPoint) >= 90) {
      return BasicActionFactory.turnBodyToPoint(player, point);
    }

    final double distance = player.getDistance(point);

    if (Math.abs(Math.sin(Math.toRadians(dirToPoint)) * distance) > maxDist) {
      return BasicActionFactory.turnBodyToPoint(player, point);
    }
    final DashAction action = BasicActionFactory.dashToPoint(player, point);
    if (staminalevel == null) {
      return action;
    }

    player.correctDashPower(action, staminalevel);
    return action;
  }

  /**
   * moves forward or backward to a position. the body dir is choosen so that
   * the player is able to see a given position.
   * 
   * @param player
   *          the player
   * @param point
   *          the point to reach
   * @param pointToWatch
   *          the point to watch
   * @param maxDist
   *          marks, when the point is reached
   * @param staminalevel
   *          the stamina level
   * @return the best action to reach a given point while watching another point
   */
  public static Action moveToPosWatching(final Player player,
      final Vektor point,
      final Vektor pointToWatch,
      final double maxDist,
      final STAMINA_LEVEL staminalevel) {

    final double dirToPoint = player.getAngleRel(point, RELATIVE.TO_BODY);
    final double dirBetweenPoints = player.getPosition().getAngleBetween(point,
        pointToWatch);

    boolean backWard = Math.abs(dirBetweenPoints) > 100;

    final double distance = player.getDistance(point);

    boolean turn = false;
    if (Math.abs(dirToPoint) > 90 != backWard) {
      turn = true;
    }
    if (Math.abs(Math.sin(Math.toRadians(dirToPoint)) * distance) > maxDist || turn) {
      if (!backWard) {
        return BasicActionFactory.turnBodyToPoint(player, point);
      }
      return BasicActionFactory.turnBackToPoint(player, point);
    }
    final DashAction action = BasicActionFactory.dashToPoint(player, point);
    if (staminalevel == null) {
      return action;
    }

    player.correctDashPower(action, staminalevel);
    return action;
  }

  /**
   * makes the player move between two positions.
   * 
   * @param player -
   *          the player
   * @param pos1 -
   *          position 1
   * @param pos2 -
   *          position 2
   * @param maxDist -
   *          when this distance to a pos is reached the player turns and moves
   *          to the other pos
   * @param staminalevel -
   *          the stamina level
   * @return an Action (Dash or Turn) (never null)
   */
  public static Action alternate(final Player player,
      final Vektor pos1,
      final Vektor pos2,
      final double maxDist,
      final STAMINA_LEVEL staminalevel) {

    final double distanceToPos1 = player.getDistance(pos1);
    final double distanceToPos2 = player.getDistance(pos2);

    // pos1 reached -> go to pos2
    if (distanceToPos1 < maxDist) {
      return BasicActionFactory.moveToPos(player, pos2, maxDist, staminalevel);
    }

    // pos2 reached -> go to pos 1
    if (distanceToPos2 < maxDist) {
      return BasicActionFactory.moveToPos(player, pos1, maxDist, staminalevel);
    }

    // if we are to far we move to the nearer position
    final double distanceP1P2 = pos1.getDistance(pos2);
    if (distanceP1P2 < distanceToPos1 - 2 && distanceP1P2 < distanceToPos2 - 2) {
      // Vektor bestPos = pos1;
      // if(distanceToPos1 > distanceToPos2)
      // bestPos = pos2;

      return BasicActionFactory.moveToPos(player, pos1, 2 * maxDist,
          staminalevel);
    }

    // check what pos we are moving towards and continue this movement
    final Vektor playerToPos1 = pos1.sub(player.getPosition());
    final Vektor playerToPos2 = pos2.sub(player.getPosition());

    final double speedToPos1 = player.getSpeed().scalar(playerToPos1);
    final double speedToPos2 = player.getSpeed().scalar(playerToPos2);

    if (speedToPos1 > speedToPos2) {
      return BasicActionFactory.moveToPos(player, pos1, maxDist, staminalevel);
    }
    return BasicActionFactory.moveToPos(player, pos2, maxDist, staminalevel);
  }

  /**
   * @param world
   *          the world
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param angle
   *          where should the ball be after freezing (relative to body)
   * @param distance
   *          where should the ball be after freezing (relative to body)
   * @param delay
   *          the number of cycles to get the ball to the stop position
   *          (as we may turn etc. before the next kick)
   * @return the best action to freeze the ball. <code>null</code>, if an
   *         opponent is too close.
   */
  public static Action freezeBall(final WorldModel world,
      final Player p,
      final Ball b,
      final double angle,
      final double distance,
      final int delay) {

    Action action = null;

    final Player p_1 = p.predictPlayerAfterAction(new KickAction(0, 0));
    final Vektor bestPosAbs = p_1.getPosition()
                          .add(new Vektor(distance, p_1.getBodyDir()+ angle));

    action = BasicActionFactory.kickInTime(p, b, bestPosAbs, delay, 0);
    // perfect kick
    if (action != null) {
      return action;
    }

    final Vektor nextBallPos = b.getPosition().cloned();
    nextBallPos.addToThis(b.getSpeed());
    
    final double angleToBall = p_1.getAngleRel(nextBallPos,
        RELATIVE.TO_BODY);

    // best we can do ;-)
    action = new KickAction(100, Vektor.normalize(180 + angleToBall));
    return action;
  }

  /**
   * This method produces an <code>Action</code> that makes the player kick
   * the ball so that it has a given velocity. Note that the action is not sent.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param velocityToReach
   *          the velocity that the ball is supposed to get
   * @return the best kick action. <code>null</code>, if the kick is
   *         impossible.
   */
  public static KickAction accelerateBallToVelocity(final Player p,
      final Ball b,
      final Vektor velocityToReach) {

    final Vektor ballSpeed = b.getSpeed();
    final Vektor neededAccel = new Vektor();
    neededAccel.x = velocityToReach.x - ballSpeed.x;
    neededAccel.y = velocityToReach.y - ballSpeed.y;

    final double neededKickPower = neededAccel.getLength() / p
        .getActualKickPowerRate(b);

    if (Double.isNaN(neededKickPower)) {
      System.out
          .println(" BasicActionFactory: accelerateBall. KICKPOWER IS NAN! " + p + " " + b);
    }

    if (neededKickPower > SConf.getInstance().MAXPOWER + 0.01) {
      return null;
    }
    return new KickAction(neededKickPower, Vektor.normalize(neededAccel
        .getAngle() - p.getBodyDir()));
  }

  
  public static TackleAction accelerateBallToVelocityTackle(final Player p,
      final Ball b,
      final Vektor velocityToReach) {

    
    Vektor velocityToReachScal = velocityToReach.cloned();
    final Vektor ballSpeed = b.getSpeed();
    final Vektor neededAccel = new Vektor();
    velocityToReachScal.setLength(3.0);
    neededAccel.x = velocityToReachScal.x - ballSpeed.x;
    neededAccel.y = velocityToReachScal.y - ballSpeed.y;

 //   final double neededKickPower = neededAccel.getLength() / p
 //       .getActualKickPowerRate(b);

 //   if (Double.isNaN(neededKickPower)) {
 //     System.out
 //         .println(" BasicActionFactory: accelerateBall. KICKPOWER IS NAN! " + p + " " + b);
 //   }

  //  if (neededKickPower > SConf.getInstance().MAXPOWER + 0.01) {
 //     return null;
 //   }
    
    System.out.println("tackleDir nA " + neededAccel + " vtr "+ velocityToReach + "vtrscal" + velocityToReachScal + " gbd " + p.getBodyDir() + " -> " + Vektor.normalize(neededAccel
        .getAngle() - p.getBodyDir()));

    return new TackleAction( Vektor.normalize(neededAccel
        .getAngle() - p.getBodyDir()));
  }

  /**
   * The KickAction needed for having the ball at a certain position in a given
   * number of cycles.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param lastPos -
   *          wanted ball position (abs)
   * @param cycles -
   *          number of cycles to reach lastPosition
   * @param error -
   *          tolerated error
   * @return - needed kickAction, <code>null</code>, if the kick is
   *         impossible.
   */
  public static KickAction kickInTime(final Player p,
      final Ball b,
      final Vektor lastPos,
      final int cycles,
      final double error) {

    if (lastPos == null) {
      return null;
    }
    if (cycles < 0) {
      return null;
    }
    // the way to go
    final double way = lastPos.getDistance(b.getPosition());
    // the max reachable ball speed in the given direction
    final Vektor maxBallAccel = p.getMaxBalAccelVektor(b, lastPos);
    if (maxBallAccel == null || maxBallAccel.getLength() == 0) {
      return null;
    }

		// the way that can be reached when the ball gets max accelerated in the
		// given direction
		final double reachableDistance = Ball.relativeBallMovementAfterNCycles(cycles)
				* maxBallAccel.getLength();
		// lastPos can't be reached -> return 'null'
		if (reachableDistance < way - error) {
			return null;
		}

    // normalize to needed speed
    final Vektor neededVelocity = new Vektor(Vektor.XY, maxBallAccel.x * way / reachableDistance,
        maxBallAccel.y * way / reachableDistance);
    return BasicActionFactory.accelerateBallToVelocity(p, b, neededVelocity);
  }

  public static TackleAction tackleTo(final Player p,
      final Ball b,
      final Vektor posToKickTo) {
//    final Vektor maxBallAccel = p.getMaxBalAccelVektor(b, posToKickTo);//    if (maxBallAccel == null) {
//      return null;
//    }
    
    Ball ball = b.cloned();
    TackleAction ta = new TackleAction(1);
    double bestAngle = Double.MAX_VALUE;
    TackleAction best = new TackleAction(1);
    double refAngle = b.getPosition().getAngleTo(posToKickTo);
    int numberOfTackles = 120;
    for (int i=0;i<numberOfTackles;i++){
      ball.copy(b);
      ta.setDirection(i*360/numberOfTackles);
      ball.predictThisAfterAction(p,ta);
      double testAngle = ball.getSpeed().getAngle();
      if (Math.abs(refAngle - testAngle)<Math.abs(refAngle - bestAngle)){
        bestAngle = testAngle;
        best.setDirection(i*360/numberOfTackles);
      }
      
    }

    if (true)
      return best;
    System.out.println("tackle2");
    final TackleAction action = BasicActionFactory.accelerateBallToVelocityTackle(p, b,
        posToKickTo.sub(b.getPosition()));
    if (action == null) {
      System.out
          .println("Strange Things in AB.kickfastest (got null): " + p + b + posToKickTo );
    }
    return action;
  }
  
  /**
   * Kicks the ball as fast as possible to the given position.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param posToKickTo
   *          the position to kick to
   * @return the best kick action to kick the ball to a given point.
   *         <code>null</code>, if the kick is impossible.
   */
  public static KickAction kickFastest(final Player p,
      final Ball b,
      final Vektor posToKickTo) {

    final Vektor maxBallAccel = p.getMaxBalAccelVektor(b, posToKickTo);
    if (maxBallAccel == null) {
      return null;
    }
    final KickAction action = BasicActionFactory.accelerateBallToVelocity(p, b,
        maxBallAccel);
    if (action == null) {
      System.out
          .println("Strange Things in AB.kickfastest (got null): " + p + b + posToKickTo + maxBallAccel);
    }
    return action;
  }

  /**
   * Kicks the ball into an other player as fast as possible.
   * 
   * @param p -
   *          kicking player
   * @param b -
   *          ball
   * @param mate -
   *          receiver
   * @param maxCycles -
   *          max number of cycles form p to mate
   * @return the kick action to get the ball to the teammate<code>null</code>,
   *         if the kick is impossible.
   */
  public static KickAction kickToPlayer(final Player p,
      final Ball b,
      final Player mate,
      final int maxCycles) {

    final Vektor maxBallAccel = p.getMaxBalAccelVektor(b, mate.getPosition());
    if (maxBallAccel == null) {
      return null;
    }

    final double distance = b.getDistance(mate);
    double wayDone = 0;
    double maxAccel = maxBallAccel.getLength();

    int cycles;

    for (cycles = 1; cycles < maxCycles; cycles++) {
      wayDone += maxAccel;
      if (wayDone > distance - 0.3) {
        break;
      }

      maxAccel *= SConf.getInstance().BALL_DECAY;
    }

    if (cycles == maxCycles) {
      return null;
    }
    return BasicActionFactory.kickInTime(p, b, mate.getPosition(), cycles, 0.4);
  }

  
  private static synchronized void initSpelling(){
    if (spelling_enable) return;
    for (int i=0;i<128;i++){
      spellings1[i]= new Vektor();
      spellings2[i]= new Vektor();
      spellings3[i]= new Vektor();
      spellings4[i]= new Vektor();
      spellings5[i]= new Vektor();      
      spellings6[i]= new Vektor();
      spellings7[i]= new Vektor();
      spellings8[i]= new Vektor();
      spellings9[i]= new Vektor();
      spellings10[i]= new Vektor();
      spellings11[i]= new Vektor();
    }
    
    // D
    spellings1['D'].y=15;
    spellings1['D'].x=-27;
    spellings2['D'].y=9;
    spellings2['D'].x=-27;
    spellings3['D'].y=3;
    spellings3['D'].x=-27;
    spellings4['D'].y=-3;
    spellings4['D'].x=-27;
    spellings5['D'].y=-9;
    spellings5['D'].x=-27;
    spellings6['D'].y=-15;
    spellings6['D'].x=-27;
    spellings7['D'].y=-12;
    spellings7['D'].x=-22;
    spellings8['D'].y=-8;
    spellings8['D'].x=-17;
    spellings9['D'].y=0;
    spellings9['D'].x=-15;
    spellings10['D'].y=8;
    spellings10['D'].x=-17;
    spellings11['D'].y=12;
    spellings11['D'].x=-22;

    // A
    spellings1['A'].y=0;
    spellings1['A'].x=-20;
    
   
    spellings2['A'].y=-15;
    spellings2['A'].x=-21;
    spellings3['A'].y=-8;
    spellings3['A'].x=-23;
    spellings4['A'].y=-1;
    spellings4['A'].x=-25;
    spellings5['A'].y=7;
    spellings5['A'].x=-27;
    spellings6['A'].y=15;
    spellings6['A'].x=-29;    

    spellings7['A'].y=-15;
    spellings7['A'].x=-19;
    spellings8['A'].y=-8;
    spellings8['A'].x=-17;
    spellings9['A'].y=-1;
    spellings9['A'].x=-15;
    spellings10['A'].y=7;
    spellings10['A'].x=-13;
    spellings11['A'].y=15;
    spellings11['A'].x=-11;


     // I
    spellings1['I'].y=-15;
    spellings1['I'].x=-21;
    spellings2['I'].y=-10;
    spellings2['I'].x=-21;
    spellings3['I'].y=-5;
    spellings3['I'].x=-21;
    spellings4['I'].y=0;
    spellings4['I'].x=-21;
    spellings5['I'].y=5;
    spellings5['I'].x=-21;
    spellings6['I'].y=10;
    spellings6['I'].x=-21;
    spellings7['I'].y=15;
    spellings7['I'].x=-21;
    spellings8['I'].y=-15;
    spellings8['I'].x=-24;
    spellings9['I'].y=-15;
    spellings9['I'].x=-18;
    spellings10['I'].y=15;
    spellings10['I'].x=-24;
    spellings11['I'].y=15;
    spellings11['I'].x=-18;
    spelling_enable =true;
  }
  
  public static MoveAction Spell(final Player p,final char c,boolean right_side){
    initSpelling();
    Vektor spelling =null;
    switch(p.getNumber()){
      case 1: spelling = spellings1[c]; break;
      case 2: spelling = spellings2[c]; break;
      case 3: spelling = spellings3[c]; break;
      case 4: spelling = spellings4[c]; break;
      case 5: spelling = spellings5[c]; break;
      case 6: spelling = spellings6[c]; break;
      case 7: spelling = spellings7[c]; break;
      case 8: spelling = spellings8[c]; break;
      case 9: spelling = spellings9[c]; break;
      case 10: spelling = spellings10[c]; break;
      case 11: spelling = spellings11[c]; break;
    }
    spelling = spelling.cloned();
    if (right_side){
      spelling.x += 40;
      spelling.rotate(180);
    }
    return new MoveAction((int)Math.round(spelling.x),(int)Math.round(spelling.y));
  }
}
